// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "entities/WorldRoad.h"
#include "entities/WorldRoadObject.h"
#include "entities/WorldTunnel.h"
#include "entities/WorldBridge.h"
#include "entities/WorldSignal.h"
#include "entities/WorldController.h"
#include "entities/WorldJunction.h"
#include "entities/WorldJunctionGroup.h"
#include "entities/WorldEntityHeader.h"
#include <unordered_set>

/**
 * Entity Management.
 */
class FWorldEntityManager
{
public:

	FWorldEntityManager();
	FWorldEntityManager(const std::string& endpointOrFile);
	~FWorldEntityManager();

	void Reset(const std::string& content);

	int GetRevision() const;

	const std::unordered_set<typename FWorldEntity::Ptr>& GetAll(const FWorldEntityMeta& meta) const;

	typename FWorldEntity::Ptr GetById(const FWorldEntityMeta& meta, std::int64_t id) const;

	void Region(double E, double W, double N, double S, int world, int rev = -1);
	void Clear();

	FWorldEntityHeader::Ptr Header() const;

	void Persist(const FWorldEntity::Ptr& entity);
	void Remove(const FWorldEntity::Ptr& entity);

	void Flush();

public:

	template<typename EntityType>
	void GetAll(std::vector<std::shared_ptr<EntityType>>& listOut) const
	{
		const auto& entities = GetAll(EntityType::sMetaClass());
		listOut.reserve(entities.size());
		for (const auto& elem : entities) listOut.emplace_back(std::static_pointer_cast<EntityType>(elem));
	}

	template<typename EntityType>
	typename EntityType::Ptr GetById(std::int64_t id) const
	{
		return std::static_pointer_cast<EntityType>(GetById(EntityType::sMetaClass(), id));
	}

	template<typename EntityType, typename ParentType>
	void Resolve(FWorldEntityReference<ParentType>& refer)
	{
		static_assert(std::is_base_of<ParentType, EntityType>::value, "Bad inheritence");
		if (refer.Get() >= 0)
		{
			refer.Resolve(GetById<EntityType>(refer.Get()));
		}
		else
		{
			typename ParentType::Ptr refee = *refer;
			if (refee && refee->ID.Get() >= 0)
			{
				refer.Get() = refee->ID.Get();
			}
		}
	}

	template<typename EntityType>
	void ResolveOneToMany(FWorldEntityArrayReference<EntityType>& refer)
	{
		std::vector<std::int64_t>& ids = refer.Get();

		// Any existing references ?
		std::vector<std::shared_ptr<EntityType>> refees;
		refees.reserve(ids.size());
		for (const auto& p : refer)
		{
			refees.push_back(*p);
		}

		for (size_t i = 0; i < ids.size(); i++)
		{
			if (ids[i] >= 0)
			{
				refees[i] = GetById<EntityType>(ids[i]);
			}
			else
			{
				if (refees[i] && refees[i]->ID.Get() >= 0)
				{
					ids[i] = refees[i]->ID.Get();
				}
			}
		}
		refer.Resolve(refees);
	}

private:
	friend class FWorldEntity;
	void EntityChanged(const FWorldEntity::Ptr& entity);

	// Hide implementation details.
	struct Opaque;
	std::shared_ptr<Opaque>	_op;

};
